package cn.asiontang.launcher;

import android.app.Activity;
import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.graphics.drawable.BitmapDrawable;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.ContextMenu;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.ListView;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import cn.asiontang.utils.ImageUtils;
import cn.asiontang.utils.ToastEx;
import cn.asiontang.utils.Utils;

/**
 * <pre>
 * 【一】界面优化速度的显示策略：（大约能控制在半秒内显示）
 * 1.使用GET_META_DATA获取所有APP列表大约30毫秒
 * 2.获取到的是使用系统默认PackageName排序规则后的数组，无法对APP名字进行排序（因为获取APP名字很耗时）
 * 3.在ListView里的getView里对显示到界面上的10几个APP单独获取名字（大约4秒/70个*10个=500毫秒）
 * 【二】放弃解决第一遍启动时速度慢的问题：
 * 1.第一遍将需要用到的数据：名字、ICON都缓存到数据库（70个APP大约5秒左右）
 * 2.之后加载全部走数据库数据（大约可控制在100~300毫秒）
 * </pre>
 */
public class MainActivity extends Activity
{
    private static final int MENU_ITEM_ID_0_RESET_APP_LIST = 0;
    private static final int MENU_ITEM_ID_1_SHOW_HIDE_APP_LIST = 1;
    private static final int MENU_ITEM_ID_2_QUICK_HIDE_APP = 2;
    private static final int MENU_ITEM_ID_3_QUICK_REFRESH_APP_LIST = 3;
    private static final int MENU_ITEM_ID_4_CLEAR_ALL_CACHE = 4;
    private static final int MENU_ITEM_ID_9_ABOUT_ME = 9;

    private int maxSize;
    private PackageManager pm;

    private MainActivityListViewAdapter mListViewAdapter;
    private AppInfo.DAO mAppInfoDAO;
    private View mProgressBar;

    private boolean isQuickHideMode = false;
    private boolean isOnlyShowHideApp = false;

    public static final String getVersionName(final Context context)
    {
        try
        {
            // 获取packagemanager的实例
            final PackageManager packageManager = context.getPackageManager();
            // getPackageName()是你当前类的包名，0代表是获取版本信息
            final PackageInfo packInfo = packageManager.getPackageInfo(context.getPackageName(), 0);

            return packInfo.versionName;
        }
        catch (final PackageManager.NameNotFoundException e)
        {
            //吃掉异常
            return "";
        }
    }

    /**
     * caching - Android: Clear Cache of All Apps? - Stack Overflow
     * http://stackoverflow.com/questions/14507092/android-clear-cache-of-all-apps#
     */
    private void clearAllCache()
    {
        final List<AppInfo> originaItems = mListViewAdapter.getOriginaItems();
        new AsyncTask<Void, Integer, Void>()
        {
            private ProgressDialog mProgressDialog;

            @Override
            protected Void doInBackground(Void... params)
            {
                /* 感觉好像没啥效果
                 * caching - Android: Clear Cache of All Apps? - Stack Overflow
                 * http://stackoverflow.com/questions/14507092/android-clear-cache-of-all-apps#
                 */
                // PackageManager pm = getPackageManager();
                // for (Method m : pm.getClass().getDeclaredMethods())
                //     //可能两种方法中只有其中一种有效,所以干脆两种都调用看看
                //     if (m.getName().equals("freeStorage") || m.getName().equals("freeStorageAndNotify"))
                //         try
                //         {
                //             long desiredFreeStorage = Long.MAX_VALUE;
                //             m.invoke(pm, desiredFreeStorage, null);
                //         }
                //         catch (Exception e)
                //         {
                //             // Method invocation failed. Could be a permission problem
                //             if (BuildConfig.DEBUG)
                //                 e.printStackTrace();
                //         }

                //最终还是通过rm -r 命令来删除所有app的缓存
                for (int i = 0; i < originaItems.size(); i++)
                {
                    if (isCancelled())
                        return null;

                    AppInfo appInfo = originaItems.get(i);
                    AppInfo.clearCacheDir(MainActivity.this, appInfo.getPackageName());
                    publishProgress(i);
                }
                return null;
            }

            @Override
            protected void onPostExecute(Void aVoid)
            {
                mProgressDialog.dismiss();
                ToastEx.show("清理全部缓存·执行完毕");
            }

            @Override
            protected void onPreExecute()
            {
                mProgressDialog = new ProgressDialog(MainActivity.this);
                mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
                mProgressDialog.setTitle("清理全部缓存中……");
                mProgressDialog.setMessage("");
                mProgressDialog.setCancelable(false);
                mProgressDialog.setCanceledOnTouchOutside(false);
                mProgressDialog.setButton(DialogInterface.BUTTON_NEGATIVE, "取消", new DialogInterface.OnClickListener()
                {
                    @Override
                    public void onClick(DialogInterface dialog, int which)
                    {
                        cancel(false);
                        ToastEx.show("清理全部缓存·被用户终止");
                    }
                });
                mProgressDialog.setMax(originaItems.size());
                mProgressDialog.show();
            }

            @Override
            protected void onProgressUpdate(Integer... values)
            {
                int index = values[0];
                AppInfo appInfo = originaItems.get(index);

                mProgressDialog.setProgress(index + 1);
                mProgressDialog.setMessage(appInfo.getAppName());
            }
        }.execute();
    }

    private AppInfo getAppInfo(ApplicationInfo info)
    {
        AppInfo tmpInfo = new AppInfo();

        tmpInfo.setIsHide(false);
        tmpInfo.setFlags(info.flags);
        tmpInfo.setIsEnabled(info.enabled);
        tmpInfo.setPackageName(info.packageName);

        //获取当前包下面可以启动的Intent耗时大约1~2毫秒
        tmpInfo.setLaunchIntent(pm.getLaunchIntentForPackage(info.packageName));

        //================================================================================
        // 获取APP名字loadLabel+图标loadIcon的速度：70多个APP，耗时4~5秒
        // 获取APP名字loadLabel的速度：70多个APP，耗时3~4秒，平均每个30~60毫秒
        // 获取APP图标loadIcon的速度：70多个APP，耗时3~4秒，平均每个30~60毫秒
        //================================================================================
        tmpInfo.setAppName(info.loadLabel(pm));

        //================================================================================
        // 压缩图标耗时：每次2~100毫秒左右根据不同APP获取到的ICON大小而定
        //================================================================================
        tmpInfo.setAppIcon(ImageUtils.getResizedImage(((BitmapDrawable) info.loadIcon(pm)).getBitmap(), maxSize, false));

        //当此APP是系统关键APP时，强制隐藏。
        tmpInfo.setIsHide(!MainActivityListViewAdapter.isAllowDisableApp(pm, tmpInfo));

        return tmpInfo;
    }

    @Override
    public boolean onContextItemSelected(MenuItem item)
    {
        return mListViewAdapter.onContextItemSelected(item);
    }

    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        maxSize = Utils.dip2px(getApplicationContext(), 48);
        pm = getPackageManager();

        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mProgressBar = findViewById(R.id.progressBar);
        mProgressBar.setVisibility(View.GONE);

        mAppInfoDAO = new AppInfo.DAO(this);

        ListView mListView = (ListView) findViewById(R.id.listView);
        mListView.setAdapter(mListViewAdapter = new MainActivityListViewAdapter(this, mListView));

        this.registerForContextMenu(mListView);

        //================================================================================
        // 从数据库读取70多个APP耗时大概100~300毫秒
        //================================================================================
        mListViewAdapter.setOriginalItems(mAppInfoDAO.getList(false));

        //假如已经事先有数据在数据库里了，就不需要显示等待圈圈了，也不需要刷新APP数据.
        if (mListViewAdapter.getCount() == 0)
            refreshAppListDatabase();
        //改为在onResume里调用
        //else
        //    refreshAppListDatabaseQuickly();
    }

    @Override
    public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo)
    {
        mListViewAdapter.onCreateContextMenu(menu, v, menuInfo);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu)
    {
        menu.add(0, MENU_ITEM_ID_3_QUICK_REFRESH_APP_LIST, 0, "刷新·APP列表");
        menu.add(0, MENU_ITEM_ID_0_RESET_APP_LIST, 0, "　　·重置APP列表");
        menu.add(0, MENU_ITEM_ID_1_SHOW_HIDE_APP_LIST, 0, "");
        menu.add(0, MENU_ITEM_ID_2_QUICK_HIDE_APP, 0, "");
        menu.add(0, MENU_ITEM_ID_4_CLEAR_ALL_CACHE, 0, "清理·当前列表APP的缓存");
        menu.add(0, MENU_ITEM_ID_9_ABOUT_ME, 0, "　　·关于");
        return true;
    }

    @Override
    protected void onDestroy()
    {
        super.onDestroy();

        //每次都exit的话，会降低下次打开APP的速度
        //System.exit(0);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item)
    {
        switch (item.getItemId())
        {
            case MENU_ITEM_ID_0_RESET_APP_LIST://重置APP列表
            {
                isOnlyShowHideApp = false;
                refreshAppListDatabase();
            }
            break;
            case MENU_ITEM_ID_1_SHOW_HIDE_APP_LIST://显示隐藏的APP列表
            {
                //显示被隐藏的APP
                if (!isOnlyShowHideApp)
                    mListViewAdapter.setOriginalItems(mAppInfoDAO.getList(true));
                else//显示正常的APP
                    mListViewAdapter.setOriginalItems(mAppInfoDAO.getList(false));

                isOnlyShowHideApp = !isOnlyShowHideApp;
                mListViewAdapter.setIsOnlyShowHideApp(isOnlyShowHideApp);

                isQuickHideMode = false;
                mListViewAdapter.setIsQuickHideMode(false);

                mListViewAdapter.refresh();
            }
            break;
            case MENU_ITEM_ID_2_QUICK_HIDE_APP://快速隐藏APP
            {
                isQuickHideMode = !isQuickHideMode;
                mListViewAdapter.setIsQuickHideMode(isQuickHideMode);
                mListViewAdapter.refresh();
            }
            break;
            case MENU_ITEM_ID_3_QUICK_REFRESH_APP_LIST://快速刷新
            {
                refreshAppListDatabaseQuickly();
            }
            break;
            case MENU_ITEM_ID_4_CLEAR_ALL_CACHE://清理所有缓存
            {
                clearAllCache();
            }
            break;
            case MENU_ITEM_ID_9_ABOUT_ME://
            {
                new AlertDialog.Builder(this)//
                        .setTitle("关于")//
                        .setMessage("作者: AsionTang\n" +
                                "官网: AsionTang.CN\n" +//
                                "版本: v" + getVersionName(this)//
                        )//
                        .setCancelable(true)//
                        .show();
                //"官网：HTTP://AsionTang.CN/YeLauncher.html#v" + getVersionName(this))//
            }
            break;
        }
        return true;
    }

    @Override
    public boolean onPrepareOptionsMenu(Menu menu)
    {
        menu.findItem(MENU_ITEM_ID_1_SHOW_HIDE_APP_LIST).setTitle(isOnlyShowHideApp
                ? "显示·未被隐藏的APP列表"
                : "显示·被隐藏的APP列表");
        menu.findItem(MENU_ITEM_ID_2_QUICK_HIDE_APP).setTitle(isOnlyShowHideApp
                ? (isQuickHideMode ? "关闭·快速显示APP" : "　　·快速显示APP")
                : (isQuickHideMode ? "关闭·快速隐藏APP" : "　　·快速隐藏APP"));
        return true;
    }

    @Override
    protected void onResume()
    {
        if (mListViewAdapter.getCount() != 0)
            refreshAppListDatabaseQuickly();
        super.onResume();
    }

    /**
     * 重建APP列表数据库
     */
    private void refreshAppListDatabase()
    {
        mProgressBar.setVisibility(View.VISIBLE);

        //开始异步线程去更新数据库里的数据。
        new AsyncTask<Void, Void, Void>()
        {
            @Override
            protected Void doInBackground(Void... params)
            {
                //================================================================================
                // 速度对比：
                // PackageManager.GET_ACTIVITIES
                //      耗时：1100~1340毫秒(获取时，或将每个包里面的Activitys都解析出来，所以速度非常的慢！)
                //      内存：18MB左右
                // PackageManager.GET_META_DATA
                //      耗时：23~90毫秒
                //      内存：8MB左右
                //================================================================================
                // getInstalledApplications 使用使用体验上要比 getInstalledPackages 快很多
                //================================================================================
                List<ApplicationInfo> packages = pm.getInstalledApplications(PackageManager.GET_META_DATA);

                List<AppInfo> list = new ArrayList<>(packages.size());
                for (ApplicationInfo info : packages)
                {
                    //排除掉自己的包名，防止自己把自己冻结掉
                    if (getPackageName().equals(info.packageName))
                        continue;
                    list.add(getAppInfo(info));
                }
                //================================================================================
                // 保存到数据库耗时：70多个APP大概需要300~500毫秒
                //================================================================================
                mAppInfoDAO.saveAll(list);

                //================================================================================
                // 从数据库读取70多个APP耗时大概100~300毫秒
                //================================================================================
                mListViewAdapter.setOriginalItems(mAppInfoDAO.getList(false));
                return null;
            }

            @Override
            protected void onPostExecute(Void aVoid)
            {
                mProgressBar.setVisibility(View.GONE);
                mListViewAdapter.refresh();
            }
        }.execute();
    }

    /**
     * 【三】快速刷新策略
     * 1.完整刷新已经做过一遍了，此时仅仅获取使用GET_META_DATA获取所有APP列表大约30毫秒
     * 2.刷新每个APP状态、删除卸载的APP、增加新APP的标题和ICO即可（节约了原APP获取标题和ICO的时间）
     */
    private void refreshAppListDatabaseQuickly()
    {
        //开始异步线程去更新数据库里的数据。
        new AsyncTask<Void, Void, Void>()
        {
            @Override
            protected Void doInBackground(Void... params)
            {
                //先获取最新APP列表；70多个APP，耗时大约20~500毫秒
                List<ApplicationInfo> newPackages = pm.getInstalledApplications(PackageManager.GET_META_DATA);

                //构建一个以packageName为Key，PackageInfo为Value的Map，70多次约7毫秒
                HashMap<String, ApplicationInfo> newAppsMap = new HashMap<>(newPackages.size());
                for (ApplicationInfo info : newPackages)
                    //排除掉自己的包名，防止自己把自己冻结掉
                    if (!getPackageName().equals(info.packageName))
                        newAppsMap.put(info.packageName, info);

                //================================================================================
                // 要处理的有两种：
                //     【1】：移除卸载的数据
                //     【2】：更新已有的数据（冻结否、系统否）
                // 要更新的有两块：
                //     【1】：数据库已有的所有APP列表
                //     【2】：当前界面的APP列表
                //================================================================================

                // 1.先刷新界面上的APP列表（提高界面响应速度）
                //   界面响应速度=getInstalledApplications速度 + 从数据库获取所有APP纯packageName列表速度 + 代码逻辑执行速度
                List<AppInfo> newAddAppList = refreshShowingAppList(newAppsMap);

                //   更新进度，可以直接刷新界面了，不用等到数据库保存完毕。耗时0
                publishProgress();

                // 2.再刷新数据库里的APP列表（以便固化APP数据）
                //   会加入上面计算好的新增加的APP，节约再次获取新APP的部分时间。
                refreshDatabaseAppList(newAppsMap, newAddAppList);
                return null;
            }

            @Override
            protected void onPostExecute(Void aVoid)
            {
            }

            @Override
            protected void onPreExecute()
            {
                mProgressBar.setVisibility(View.VISIBLE);
            }

            @Override
            protected void onProgressUpdate(Void... values)
            {
                mProgressBar.setVisibility(View.GONE);
                mListViewAdapter.refresh();
            }

            private void refreshDatabaseAppList(Map<String, ApplicationInfo> newAppsMap, List<AppInfo> newAddAppList)
            {
                List<AppInfo> oldPackages = mAppInfoDAO.getAllList();
                HashMap<String, AppInfo> oldAppsMap = new HashMap<>(oldPackages.size());

                //找到已经被卸载的APP
                ArrayList<AppInfo> needDeleteApps = new ArrayList<>();
                for (AppInfo p : oldPackages)
                {
                    //顺便构建一个以packageName为Key，AppInfo为Value的Map，节约一次循环
                    oldAppsMap.put(p.getPackageName(), p);

                    //当此APP不在最新APP列表里时，说明被卸载了。
                    if (newAppsMap.containsKey(p.getPackageName()))
                        continue;
                    needDeleteApps.add(p);
                }
                for (AppInfo p : needDeleteApps)
                    oldPackages.remove(p);

                //在“正常模式”，会接收来自界面更新得到的新增加APP列表，因此不需要再获取一遍
                for (AppInfo p : newAddAppList)
                    oldPackages.add(p);

                //将APP的最新状态更新到界面和数据库
                for (ApplicationInfo info : newAppsMap.values())
                {
                    AppInfo tmpInfo = oldAppsMap.get(info.packageName);
                    if (tmpInfo != null)
                    {
                        //旧APP，更新其它状态即可
                        tmpInfo.setFlags(info.flags);
                        tmpInfo.setIsEnabled(info.enabled);
                        continue;
                    }

                    //在“正常模式”，会接收来自界面更新得到的新增加APP列表，因此不需要再获取一遍
                    if (isOnlyShowHideApp)
                        oldPackages.add(getAppInfo(info));
                }
                //以上逻辑代码耗时：36毫秒
                //================================================================================
                // 保存到数据库耗时：70多个APP大概需要300~600毫秒
                //================================================================================
                mAppInfoDAO.saveAll(oldPackages);
            }

            private List<AppInfo> refreshShowingAppList(HashMap<String, ApplicationInfo> newAppsMap)
            {
                List<AppInfo> newAddAppList = new ArrayList<>();

                List<AppInfo> oldPackages = mListViewAdapter.getOriginaItems();
                HashMap<String, AppInfo> oldAppsMap = new HashMap<>(oldPackages.size());

                //找到已经被卸载的APP
                ArrayList<AppInfo> needDeleteApps = new ArrayList<>();
                for (AppInfo p : oldPackages)
                {
                    //顺便构建一个以packageName为Key，AppInfo为Value的Map，节约一次循环
                    oldAppsMap.put(p.getPackageName(), p);

                    //当此APP不在最新APP列表里时，说明被卸载了。
                    if (newAppsMap.containsKey(p.getPackageName()))
                        continue;
                    needDeleteApps.add(p);
                }
                for (AppInfo p : needDeleteApps)
                    oldPackages.remove(p);

                //从数据库获取所有APP的纯PackageName列表，以便得知哪些APP是新安装的
                List<String> allAppPackageNameList = mAppInfoDAO.getAllAppPackageNameList();
                AppInfo tmpInfo;
                for (ApplicationInfo info : newAppsMap.values())
                {
                    //APP还存在
                    if (allAppPackageNameList.contains(info.packageName))
                    {
                        //且在当前显示列表
                        tmpInfo = oldAppsMap.get(info.packageName);
                        if (tmpInfo != null)
                        {
                            //更新其它状态即可（图标、APP名字等修改的概率很低，获取时间又耗时的，在这里故意忽略不更新）
                            tmpInfo.setFlags(info.flags);
                            tmpInfo.setIsEnabled(info.enabled);
                        }
                        //不在当前显示列表，则无需更新
                    }
                    //APP是新安装的
                    else
                    {
                        //在“显示隐藏APP列表”界面视图时，不关心新安装的APP
                        if (isOnlyShowHideApp)
                            continue;

                        tmpInfo = getAppInfo(info);

                        //新增的默认显示在最上面
                        oldPackages.add(0, tmpInfo);
                        newAddAppList.add(tmpInfo);
                    }
                }
                return newAddAppList;
            }
        }.execute();
    }
}
